#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"

// 服务器默认监听的端口号
uint16_t defaultport = 8080;
// 服务器监听所有网卡的请求  服务器可能会有多张网卡
std::string defaultip = "0.0.0.0";
// 定义接收缓冲区大小
const int size = 1024;
// 枚举错误类型
enum
{
    SOCKET_ERR = 1, // 创建socket失败
    BIND_ERR        // 绑定失败
};

// UdpServer类，用于实现UDP服务器
class UdpServer
{
public:
    // 构造函数，初始化服务器
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip)
        : sockfd_(0), ip_(ip), port_(port)
    {
    }
    // 初始化服务器
    void Init()
    {
        // 1.创建udp socket
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 创建socket套接字  AF_INET是ipv4  SOCK_DGRAM不可靠传输(udp)
        if (sockfd_ < 0)                          // 返回值小于0创建失败
        {
            lg(Fatal, "socket create error ,sockfd: %d", sockfd_); // 打印日志
            exit(SOCKET_ERR);                                      // 设置错误码并退出
        }
        lg(Info, "socket create success,sockfd: %d", sockfd_); // 到这里说明socket创建成功，输出一条消息

        // 2. bind socket //开始绑定
        struct sockaddr_in local;     // 创建一个socket结构体对象
        bzero(&local, sizeof(local)); // 把对象清0

        local.sin_family = AF_INET; // 设置ipv4
        // 设置服务器端口号  htons将port转换成网络序列（大端）
        local.sin_port = htons(port_); // 需要保证我的端口号是网络字节序列，端口号是要给对方发送的
        // 设置ipv4 同样也是要设置成网络序列  --inet_addr函数
        //  local.sin_addr.s_addr = inet_addr(ip_.c_str());// 1. string -> uint32_t 2. uint32_t必须是网络序列
        local.sin_addr.s_addr = htonl(INADDR_ANY); // 下面这种也可以 INADDR_ANY就是0.0.0.0
        // 上面两个二选一都可以
        // int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        // 第二个参数这里要强转
        if (bind(sockfd_, (const sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, sockfd: %d", sockfd_);
            exit(BIND_ERR);
        }

        lg(Info, "bind success");
    }
    // 运行服务器
    void Run() 
    {

        char inbuffer[size];
        while (1)
        {
            struct sockaddr_in client; // 创建一个socket对象用来接收客户端信息
            socklen_t len = sizeof(client);
            // 用来接收客户端的消息存在inbuffer 然后将client传进去，它可以帮我们把客户端的信息带出来，client是一个struct sockaddr_in结构体对象
            // 里面是可以存端口号 IP地址什么的
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&client, &len);
            if (n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d,err string: %s", errno, strerror(errno));
                continue;
            }
            // 下面两行可以获取是谁连接的我
            uint16_t clientport = ntohs(client.sin_port);      // 客户端的端口号
            std::string clientip = inet_ntoa(client.sin_addr); // 客户端的ip
            inbuffer[n] = 0;
            std::string info = inbuffer;
            std::string echo_string = Handler(info,clientip,clientport); // 将客户端的发的信息传给回调函数
            //然后将构建的消息返回给客户端，"Server get a message: "+客户端发的消息
            //ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,  const struct sockaddr *dest_addr, socklen_t addrlen);
            sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&client, len);//强转
        }
    }

    std::string Handler(const std::string &str,const std::string& clientip,const uint16_t port)
    {
        // 构建响应消息
        std::string res = "Server get a message: ";
        res += str;
        std::cout <<"[clientip:"<<clientip<<",clientport:"<<port<<"]"<< res << std::endl;
        return res;
    }
    // 销毁服务器
    ~UdpServer()
    {
        if (sockfd_ > 0)
            close(sockfd_);//关闭sockefd就相当于关闭服务器了
    }

private:
    int sockfd_;     // 网络文件描述符
    std::string ip_; // 任意地址 bind 0
    uint16_t port_;  // 表明服务器进程的端口号
};